package ide.ccf.ccproject.qmake;

import ide.ccf.ccproject.CCFProject;
import ide.ccf.ccproject.ProjectPlugin;
import ide.ccf.ccproject.common.Utils;
import ide.ccf.ccproject.preferences.CCFPreferencePage;
import ide.ccf.ccproject.preferences.PreferenceConstants;
import ide.ccf.ccproject.wizards.CCFCommonProjectWizard;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.CommandLauncher;
import org.eclipse.cdt.core.ConsoleOutputStream;
import org.eclipse.cdt.core.resources.IConsole;
import org.eclipse.cdt.utils.spawner.EnvironmentReader;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PreferencesUtil;

public class QMakeRunner 
{
	private static final boolean WINDOWS = java.io.File.separatorChar == '\\';
	private static final String QMAKE_PROBLEM = "ide.ccf.ccproject.problem";
	public static boolean bIsSetPreferences = false;
	/**
	 * 执行qmake
	 * @param project
	 * @param monitor
	 * @return
	 */
 	public static String runQMake(IProject project, IProgressMonitor monitor)
	{
		checkCancel(monitor);
 		removeQMakeErrors(project);

		try 
		{
 			String errStr = QMakeRunner.runQMake(project, null, monitor);
 			
 			if (errStr != null) 
 			{
 				reportQMakeError(project, errStr);
 				return errStr;
			}
 		} 
		catch (CoreException e) 
		{
 			e.printStackTrace();
 			reportQMakeError(project, e.getLocalizedMessage());
 			return e.getLocalizedMessage();
		}
 		return null;
	}
	
 	/**
 	 * 检查是否取消
 	 * @param monitor
 	 */
 	public static void checkCancel(IProgressMonitor monitor) 
 	{
		if (monitor != null && monitor.isCanceled())
			throw new OperationCanceledException();
	}
 	
 	/**
 	 * 删除qmake错误
 	 * @param project
 	 */
 	private static void removeQMakeErrors(IProject project) 
 	{
		try 
		{
 			project.deleteMarkers(QMAKE_PROBLEM, false, IResource.DEPTH_ZERO);
		} 
		catch (CoreException ex) 
		{
			
		}
	}
	
 	/**
 	 * 显示qmake错误
 	 * @param project
 	 * @param message
 	 */
 	private static void reportQMakeError(IProject project, String message) 
 	{
		try 
		{
 			IMarker marker = project.createMarker(QMAKE_PROBLEM);
			marker.setAttribute(IMarker.MESSAGE, message);
			marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
		} 
		catch (CoreException ex) 
		{
			
		}		
	}	
	
 	/**
 	 * 为指定项目执行qmake
 	 * @param project
 	 * @param envMods
 	 * @param monitor
 	 * @return
 	 * @throws CoreException
 	 */
	@SuppressWarnings("unused")
	public static String runQMake(IProject project, Map<String, String> envMods, IProgressMonitor monitor) throws CoreException 
	{
		IPath proFilePath = Utils.findProFile(project);
		
		if (proFilePath == null) 
		{
			return "无法定位项目文件";
		}

		IPath buildCommand = getQMakeCommand(project);
		//System.out.println(buildCommand.toString());
		if (buildCommand == null) 
		{
			return "无法执行qmake（没有设置CCF版本）。打开“首选项->CCF”并设置要使用的CCF版本。";
		}

		IConsole console = CCorePlugin.getDefault().getConsole();
		console.start(project);
		ConsoleOutputStream consoleOut = console.getOutputStream();
		ConsoleOutputStream consoleErr = console.getErrorStream();

		// 创建运行环境
		Properties envProps = EnvironmentReader.getEnvVars();
		IPath workingDir = Utils.removeFileName(proFilePath);
		envProps.setProperty("CWD", workingDir.toOSString());
		envProps.setProperty("PWD", workingDir.toOSString());

		// 允许注册扩展修改环境，比如EPOCROOT
		for (IQMakeEnvironmentModifier envModifier : ProjectPlugin.getDefault().getEnvironmentModifierExtensions()) 
		{
			envProps = envModifier.getModifiedEnvironment(project, envProps);
		}

		if (envMods != null) 
		{
			for (String name : envMods.keySet()) 
			{
				envProps.setProperty(name, envMods.get(name));
			}
		}
		
		String errStr = runQMake(proFilePath, buildCommand, consoleOut, consoleErr, envProps, monitor);
		
		if (errStr == null) 
		{
			try 
			{
				project.refreshLocal(IResource.DEPTH_INFINITE, null);
			} 
			catch (CoreException e) 
			{
			}
		}
		
		markGeneratedFilesAsDerived(project);
		
		return errStr;
	}
	
	/**
	 * 标记一些生成文件为衍生文件
	 * 理想情况应该是使用一些API来获取真正的生成文件列表。这里只是标记所有的bld.inf和“*.mmp”文件为衍生文件
	 * @param project
	 */
	public static void markGeneratedFilesAsDerived(IProject project) 
	{
		try 
		{
			project.accept(new IResourceVisitor() 
			{
				@SuppressWarnings("deprecation")
				public boolean visit(IResource resource) throws CoreException 
				{
					if (resource instanceof IFile) 
					{
						String name = resource.getName().toLowerCase();
						
						if (name.equals("bld.inf") || name.endsWith(".mmp")) 
						{
							resource.setDerived(true);
						}
						
						return false;
					}
					return true;
				}
			}, IResource.DEPTH_INFINITE, IContainer.EXCLUDE_DERIVED);
		} 
		catch (CoreException e) 
		{
			e.printStackTrace();
		}
	}
	
	/**
	 * 为指定项目文件执行qmake
	 * @param projectFilePath
	 * @param buildCommand
	 * @param stdout
	 * @param stderr
	 * @param envProps
	 * @param monitor
	 * @return
	 */
	@SuppressWarnings("deprecation")
	private static String runQMake(IPath projectFilePath, IPath buildCommand, ConsoleOutputStream stdout, ConsoleOutputStream stderr, Properties envProps, IProgressMonitor monitor) 
	{
		if (monitor == null) 
		{
			monitor = new NullProgressMonitor();
		}
		
		SubMonitor subMonitor = SubMonitor.convert(monitor, 2);
		subMonitor.newChild(1).subTask("QMake - 收集数据");

		CommandLauncher launcher = new CommandLauncher();
		launcher.showCommand(true);
		
		String errMsg = null;
		
		List<String> args = new ArrayList<String>();
		args.add("-recursive");
		
		if (!WINDOWS) 
		{
			args.add("CONFIG+=debug_and_release");			
		}
		
		args.add(Utils.getFileName(projectFilePath, false));
		IPath workingDir = Utils.removeFileName(projectFilePath);
		
		Process p = launcher.execute(buildCommand, args.toArray(new String[args.size()]), createEnvStringList(envProps), workingDir);
		
		if (p != null) 
		{
			
			try 
			{
				p.getOutputStream().close();
			} 
			catch (IOException e) 
			{
				
			}
			
			subMonitor.newChild(1).subTask("QMake - 开始执行: " + launcher.getCommandLine());
						
			if (launcher.waitAndRead(stdout, stderr, new SubProgressMonitor(monitor, 0)) != CommandLauncher.OK)
				errMsg = launcher.getErrorMessage();
			
			if (p.exitValue() != 0) 
			{
				String outputStr = stderr.readBuffer();
				System.out.println("outputStr:"+outputStr);
				if (outputStr != null && outputStr.length() > 0) 
				{
					errMsg = outputStr;
				} 
				else 
				{
					outputStr = stdout.readBuffer();
					
					if (outputStr != null && outputStr.length() > 0) 
					{
						errMsg = outputStr;
					} 
					else 
					{
						errMsg = "处理文件出错：'" + projectFilePath + "'";
					}
				}
			} 
			else 
			{
				ProjectPlugin.getDefault().clearRunningQMakeRequest(projectFilePath.toOSString());
			}

			try 
			{
				stdout.close();
				stderr.close();
			} 
			catch (IOException e) 
			{
				e.printStackTrace();
			}
		} 
		else 
		{
			errMsg = launcher.getErrorMessage();
		}
		monitor.done();

		return errMsg;
	}

	/**
	 * 获取qmake的绝对路径以运行qmake
	 * @param project
	 * @return
	 */
	private static IPath getQMakeCommand(IProject project) 
	{
		String binPath = null;
		if (project != null) 
		{
			CCFProject ccfProject = new CCFProject(project);
			binPath = ccfProject.getBinPath();
		} 
		else 
		{
			String defaultVersion = ProjectPlugin.getDefault().getPreferenceStore().getString(PreferenceConstants.CCFVERSION_DEFAULT);
			binPath = CCFPreferencePage.getCCFVersionBinPath(defaultVersion);
		}
		
		if (binPath == null) 
		{
			if(CCFCommonProjectWizard.bIsSetPreferences == false)
			{
				PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() 
				{
					public void run() 
					{
						Shell shell = PlatformUI.getWorkbench().getDisplay().getActiveShell();
						MessageDialog dialog = new MessageDialog(shell, "构建错误", null, "未设置默认CCF版本。打开首选项对话框中的CCF页面并添加CCF版本。", MessageDialog.ERROR, new String[] { "打开首选项", IDialogConstants.CANCEL_LABEL }, 0);
						bIsSetPreferences = true;
						if (dialog.open() == 1)
				        	return;
						
						PreferencesUtil.createPreferenceDialogOn(shell, "ide.ccf.ccproject.preferences.CCFPreferencePage", null, null).open();
					}
				});
			}
			CCFCommonProjectWizard.bIsSetPreferences  = false;
	
			
			if (project != null) 
			{
				CCFProject cCFProject = new CCFProject(project);
				binPath = cCFProject.getBinPath();
			} 
			else 
			{
				String defaultVersion = ProjectPlugin.getDefault().getPreferenceStore().getString(PreferenceConstants.CCFVERSION_DEFAULT);
				binPath = CCFPreferencePage.getCCFVersionBinPath(defaultVersion);
			}
			
			if (binPath == null) 
			{
				return null;
			}
		}

		return new Path(binPath).append("qmake-qt4");
	}

	/**
	 * 创建环境字符串列表
	 * @param envProps
	 * @return
	 */
	private static String[] createEnvStringList(Properties envProps) 
	{
		String[] env = null;
		List<String> envList = new ArrayList<String>();
		Enumeration<?> names = envProps.propertyNames();
		
		if (names != null) 
		{
			while (names.hasMoreElements()) 
			{
				String key = (String) names.nextElement();
				envList.add(key + "=" + envProps.getProperty(key));
			}
			env = (String[]) envList.toArray(new String[envList.size()]);
		}
		return env;
	}
}

